#include <vector>
#include <initguid.h>
#include <d3d9.h>
#include <dxva2api.h>
#include "Log/SystemLog.h"
#include "VideoD3dRender.h"

// Direct3D format table
static const D3dFormat D3dFormatTable[] = {

	{ "YV12", (D3DFORMAT)MAKEFOURCC('Y', 'V', '1', '2'), AV_PIX_FMT_YUV420P },

	{ "NV12", (D3DFORMAT)MAKEFOURCC('N', 'V', '1', '2'), AV_PIX_FMT_NV12 },

	{ NULL, (D3DFORMAT)0, AV_PIX_FMT_NONE }
};

// Define all the GUIDs used directly here to avoid problems with inconsistent dxva2api.h versions in mingw - w64 and different MSVC version
DEFINE_GUID(IID_IDirectXVideoDecoderService, 0xfc51a551, 0xd5e7, 0x11d9, 0xaf, 0x55, 0x00, 0x05, 0x4e, 0x43, 0xff, 0x02);
DEFINE_GUID(DXVA2_ModeMPEG2_VLD, 0xee27417f, 0x5e28, 0x4e65, 0xbe, 0xea, 0x1d, 0x26, 0xb5, 0x08, 0xad, 0xc9);
DEFINE_GUID(DXVA2_ModeMPEG2and1_VLD, 0x86695f12, 0x340e, 0x4f04, 0x9f, 0xd3, 0x92, 0x53, 0xdd, 0x32, 0x74, 0x60);
DEFINE_GUID(DXVA2_ModeH264_E, 0x1b81be68, 0xa0c7, 0x11d3, 0xb9, 0x84, 0x00, 0xc0, 0x4f, 0x2e, 0x73, 0xc5);
DEFINE_GUID(DXVA2_ModeH264_F, 0x1b81be69, 0xa0c7, 0x11d3, 0xb9, 0x84, 0x00, 0xc0, 0x4f, 0x2e, 0x73, 0xc5);
DEFINE_GUID(DXVADDI_Intel_ModeH264_E, 0x604F8E68, 0x4951, 0x4C54, 0x88, 0xFE, 0xAB, 0xD2, 0x5C, 0x15, 0xB3, 0xD6);
DEFINE_GUID(DXVA2_ModeVC1_D, 0x1b81beA3, 0xa0c7, 0x11d3, 0xb9, 0x84, 0x00, 0xc0, 0x4f, 0x2e, 0x73, 0xc5);
DEFINE_GUID(DXVA2_ModeVC1_D2010, 0x1b81beA4, 0xa0c7, 0x11d3, 0xb9, 0x84, 0x00, 0xc0, 0x4f, 0x2e, 0x73, 0xc5);
DEFINE_GUID(DXVA2_ModeHEVC_VLD_Main, 0x5b11d51b, 0x2f4c, 0x4452, 0xbc, 0xc3, 0x09, 0xf2, 0xa1, 0x16, 0x0c, 0xc0);
DEFINE_GUID(DXVA2_ModeHEVC_VLD_Main10, 0x107af0e0, 0xef1a, 0x4d19, 0xab, 0xa8, 0x67, 0xa1, 0x63, 0x07, 0x3d, 0x13);
DEFINE_GUID(DXVA2_ModeVP9_VLD_Profile0, 0x463707f8, 0xa1d0, 0x4585, 0x87, 0x6d, 0x83, 0xaa, 0x6d, 0x60, 0xb8, 0x9e);
DEFINE_GUID(DXVA2_NoEncrypt, 0x1b81beD0, 0xa0c7, 0x11d3, 0xb9, 0x84, 0x00, 0xc0, 0x4f, 0x2e, 0x73, 0xc5);
DEFINE_GUID(GUID_NULL, 0x00000000, 0x0000, 0x0000, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00);


// Dxva2 mode table
static const Dxva2Mode Dxva2ModeTable[] = {

	/* MPEG-2 */
	{ &DXVA2_ModeMPEG2_VLD, AV_CODEC_ID_MPEG2VIDEO },

	{ &DXVA2_ModeMPEG2and1_VLD, AV_CODEC_ID_MPEG2VIDEO },

	/* H.264 */
	{ &DXVA2_ModeH264_F, AV_CODEC_ID_H264 },

	{ &DXVA2_ModeH264_E, AV_CODEC_ID_H264 },

	/* Intel specific H.264 mode */
	{ &DXVADDI_Intel_ModeH264_E, AV_CODEC_ID_H264 },

	/* VC-1 / WMV3 */
	{ &DXVA2_ModeVC1_D2010, AV_CODEC_ID_VC1 },

	{ &DXVA2_ModeVC1_D2010, AV_CODEC_ID_WMV3 },

	{ &DXVA2_ModeVC1_D, AV_CODEC_ID_VC1 },

	{ &DXVA2_ModeVC1_D, AV_CODEC_ID_WMV3 },

	/* HEVC/H.265 */
	{ &DXVA2_ModeHEVC_VLD_Main, AV_CODEC_ID_HEVC },

	{ &DXVA2_ModeHEVC_VLD_Main10, AV_CODEC_ID_HEVC },

	/* VP8/9 */
	{ &DXVA2_ModeVP9_VLD_Profile0, AV_CODEC_ID_VP9 },

	{ NULL, AV_CODEC_ID_NONE }
};

// Construct the VideoD3dRender
VideoD3dRender::VideoD3dRender() :
	m_pWinHandle(NULL),
	m_pDecCodecCtx(NULL),
	m_bIsHaveInitRender(false),
	m_bDisposed(false)
{
	Initialize();
}

// Detructe the VideoD3dRender
VideoD3dRender::~VideoD3dRender()
{
	Destory();
}

// Initialize the decoder
void VideoD3dRender::Initialize()
{

}

// Destory the decoder
void VideoD3dRender::Destory()
{
	if (!GetDisposed())
	{
		SetDisposed(true);

		// Release render
		ReleaseRender();
	}
}

// Release D3d context
void VideoD3dRender::ReleaseD3dContext()
{
	AVCodecContext* pAvContext = GetDecCodecCtx();

	if (pAvContext == NULL)
	{
		return;
	}

	InputStream* pStream = (InputStream*)pAvContext->opaque;

	if (pStream)
	{
		Dxva2Context* pDxva2Context = (Dxva2Context*)pStream->pHwaccelContext;

		if (pDxva2Context)
		{
			// Destory all surfaces
			if (pDxva2Context->pSurfaces)
			{
				for (int index = 0; index < pDxva2Context->SurfaceNum; ++index)
				{
					if (pDxva2Context->pSurfaces[index])
					{
						IDirect3DSurface9_Release(pDxva2Context->pSurfaces[index]);
					}
				}
			}

			av_free(&pDxva2Context->pSurfaces);

			av_free(&pDxva2Context->pSurefaceInfo);

			pDxva2Context->SurfaceNum = 0;

			pDxva2Context->SurfaceAge = 0;

			// Destory the direct video decoder
			if (pDxva2Context->pDirectVideoDecoder)
			{
				pDxva2Context->pDirectVideoDecoder->Release();

				pDxva2Context->pDirectVideoDecoder = NULL;
			}
		}
	}
}

// Release render
void VideoD3dRender::ReleaseRender()
{
	AVCodecContext* pAvContext = GetDecCodecCtx();

	if (pAvContext == NULL)
	{
		return;
	}

	InputStream* pStream = (InputStream*)pAvContext->opaque;

	if (pStream == NULL)
	{
		return;
	}

	pStream->hwaccel_uninit = NULL;

	pStream->hwaccel_get_buffer = NULL;

	pStream->hwaccel_retrieve_data = NULL;

	Dxva2Context* pDxva2Context = (Dxva2Context*)pStream->pHwaccelContext;

	if (pDxva2Context == NULL)
	{
		return;
	}

	// Release direct3d video decoder
	if (pDxva2Context->pDirectVideoDecoder)
	{
		ReleaseD3dContext();
	}

	// Release direct3d decoder service
	if (pDxva2Context->pDirectVideoDecoderService)
	{
		pDxva2Context->pDirectVideoDecoderService->Release();

		pDxva2Context->pDirectVideoDecoderService = NULL;
	}

	// Close direct3d device
	if (pDxva2Context->pD3dDeviceManager
		&& pDxva2Context->hD3dDevice != INVALID_HANDLE_VALUE)
	{
		pDxva2Context->pD3dDeviceManager->CloseDeviceHandle(pDxva2Context->hD3dDevice);
	}

	// Release direct3d device manager
	if (pDxva2Context->pD3dDeviceManager)
	{
		pDxva2Context->pD3dDeviceManager->Release();

		pDxva2Context->pD3dDeviceManager = NULL;
	}

	// Release direct3d device
	if (pDxva2Context->pD3dDevice)
	{
		IDirect3DDevice9_Release(pDxva2Context->pD3dDevice);
	}

	// Release direct3d
	if (pDxva2Context->pD3d)
	{
		IDirect3D9_Release(pDxva2Context->pD3d);
	}

	// Free the direct3d library
	if (pDxva2Context->hD3dLib)
	{
		FreeLibrary(pDxva2Context->hD3dLib);
	}

	// Free the dxva2 library
	if (pDxva2Context->hDxva2Lib)
	{
		FreeLibrary(pDxva2Context->hDxva2Lib);
	}

	// Free the temp frame
	av_frame_free(&pDxva2Context->pTempFrame);

	// Free the hard accelerate context
	av_freep(&pStream->pHwaccelContext);

	av_freep(&pAvContext->hwaccel_context);

	// Delete the section lock
	DeleteCriticalSection(&m_SectionLock);
}

// Release buffer
void VideoD3dRender::Dxva2ReleaseBuffer(void* pUserData, uint8_t* data)
{
	Dxva2SurfaceWrapper* pWrapper = (Dxva2SurfaceWrapper*)pUserData;

	if (pWrapper == NULL)
	{
		return;
	}

	Dxva2Context* pDxva2Context = pWrapper->pDxva2Context;

	if (pDxva2Context == NULL)
	{
		return;
	}

	for (int index = 0; index < pDxva2Context->SurfaceNum; ++index)
	{
		if (pDxva2Context->pSurfaces[index] == pWrapper->CurSurface)
		{
			pDxva2Context->pSurefaceInfo[index].iUsed = 0;

			break;
		}
	}

	IDirect3DSurface9_Release(pWrapper->CurSurface);

	pWrapper->pDirectVideoDecoder->Release();

	av_free(pWrapper);
}

// Get buffer 
int VideoD3dRender::Dxva2GetBuffer(AVCodecContext* pAvContext, AVFrame* pFrame, int iFlags)
{
	if (pAvContext == NULL)
	{
		return -1;
	}

	if (pFrame == NULL)
	{
		return -2;
	}

	InputStream* pStream = (InputStream*)pAvContext->opaque;

	if (pStream == NULL)
	{
		return;
	}

	Dxva2Context* pDxva2Context = (Dxva2Context*)pStream->pHwaccelContext;

	if (pDxva2Context == NULL)
	{
		return;
	}

	// Input frame's pixel format should be yuv420p
	av_assert0(pFrame->format == AV_PIX_FMT_DXVA2_VLD);

	// Find an available surface for using
	int iOldSurfaceIndex = -1;

	for (int index = 0; index < pDxva2Context->SurfaceNum; ++index)
	{
		SurfaceInfo* pInfo = &pDxva2Context->pSurefaceInfo[index];

		if (!pInfo->iUsed
			&& (iOldSurfaceIndex == -1 || pInfo->iAge < pDxva2Context->pSurefaceInfo[iOldSurfaceIndex].iAge))
		{
			iOldSurfaceIndex = index;
		}
	}

	if (iOldSurfaceIndex == -1)
	{
		LOG_ERROR_EX("No free DXVA2 surface!");

		return AVERROR(ENOMEM);
	}

	int iCurAvailableIndex = iOldSurfaceIndex;

	LPDIRECT3DSURFACE9 CurSurface = pDxva2Context->pSurfaces[iCurAvailableIndex];

	// Create surface wrapper
	Dxva2SurfaceWrapper* pWrapper = (Dxva2SurfaceWrapper*)av_mallocz(sizeof(Dxva2SurfaceWrapper));

	if (pWrapper == NULL)
	{
		return AVERROR(ENOMEM);
	}

	// Create hard decode buffer for the frame
	pFrame->buf[0] = av_buffer_create((uint8_t*)CurSurface,
		0,
		Dxva2ReleaseBuffer,
		pWrapper,
		AV_BUFFER_FLAG_READONLY);

	if (pFrame->buf[0] == NULL)
	{
		av_free(pWrapper);

		return AVERROR(ENOMEM);
	}

	// Set the wrapper here
	pWrapper->pDxva2Context = pDxva2Context;

	pWrapper->CurSurface = CurSurface;

	IDirect3DSurface9_AddRef(pWrapper->CurSurface);

	pWrapper->pDirectVideoDecoder = pDxva2Context->pDirectVideoDecoder;

	pWrapper->pDirectVideoDecoder->AddRef();

	pDxva2Context->pSurefaceInfo[iCurAvailableIndex].iUsed = 1;

	pDxva2Context->pSurefaceInfo[iCurAvailableIndex].iAge = pDxva2Context->SurfaceAge++;

	pFrame->data[3] = (uint8_t*)CurSurface;

	return 0;
}

// Retrieve data
int VideoD3dRender::Dxva2RetrieveData(AVCodecContext* pAvContext, AVFrame* pFrame)
{

}

// Get hard pixel format
AVPixelFormat VideoD3dRender::GetHardPixelFormat(AVCodecContext* pAvContext, const enum AVPixelFormat* pPixelFormat)
{
	InputStream* pStream = (InputStream*)pAvContext->opaque;

	if (pStream)
	{
		pStream->eActiveHwAccelId = HWACCEL_DXVA2;

		pStream->HwPixelFormat = AV_PIX_FMT_DXVA2_VLD;
	}

	return AV_PIX_FMT_DXVA2_VLD;
}

// Configure the render
bool VideoD3dRender::Init(AVCodecContext* pAvctx, void* pWindowHandle)
{
	if (GetIsHaveInitRender())
	{
		return true;
	}


	return true;
}

// Render the data
bool VideoD3dRender::Render(AVFrame* pFrame)
{
	if (pFrame == NULL)
	{
		return false;
	}

	return true;
}
